/**
 * Created on 2018年7月20日 by caiming
 */
package com.netty.fileupload.handler;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.text.SimpleDateFormat;

import com.fastDFS.FastDFSTemplate;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

/**
 * @author caiming
 * @version 1.0
 * @Title
 * @Description
 * @修改记录
 * @修改序号，修改日期，修改人，修改内容
 */
public class FileWebSocketFrameHandler extends SimpleChannelInboundHandler<Object> {
    //文件上传路经
    private final static String SERVER_SAVE_PATH = "D:/";

    private static final String WEBSOCKET_PATH = "/ws";

    private WebSocketServerHandshaker handshaker;

    private String fileName = null;

    private String fileId;

    private static final File INDEX;

    private FastDFSTemplate fastDFSTemplate;

    static {
        URL location = FileWebSocketFrameHandler.class.getProtectionDomain().getCodeSource().getLocation();// 当前class的绝对路径
        String path;
        try {
            path = location.toURI() + "index.html";
            path = !path.contains("file:") ? path : path.substring(5);
            INDEX = new File(path);
        } catch (URISyntaxException e) {
            throw new IllegalStateException("Unable to locate index.html", e);
        }

    }

    public FileWebSocketFrameHandler(FastDFSTemplate fastDFSTemplate) {
        this.fastDFSTemplate = fastDFSTemplate;
    }

    private void handleHttpRequest(ChannelHandlerContext ctx, HttpRequest req) throws Exception {
        // 100-continue 是用于客户端在发送 post 数据给服务器时，征询服务器情况，看服务器是否处理 post
        // 的数据，如果不处理，客户端则不上传 post 是数据，反之则上传。在实际应用中，通过 post 上传大数据时，才会使用到
        // 100-continue 协议。
        if (HttpUtil.is100ContinueExpected(req)) {
            send100Continue(ctx);
        }
        RandomAccessFile file = new RandomAccessFile(INDEX, "r");
        HttpResponse response = new DefaultHttpResponse(req.protocolVersion(), HttpResponseStatus.OK);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html;charset=UTF-8");
        // 当使用Keep-Alive模式（又称持久连接、连接重用）时，Keep-Alive功能使客户端到服
        // 务器端的连接持续有效，当出现对服务器的后继请求时，Keep-Alive功能避免了建立或者重新建立连接。
        boolean keepAlive = HttpUtil.isKeepAlive(req);
        if (keepAlive) {
            // Keep-Alive模式，客户端如何判断请求所得到的响应数据已经接收完成（或者说如何知道服务器已经发生完了数据）？
            // Conent-Length表示实体内容长度，客户端（服务器）可以根据这个值来判断数据是否接收完成
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, file.length());
            response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
        }

        ctx.write(response);

        // 如果不需要SSL加密，可以使用region零拷贝特性
        if (ctx.pipeline().get(SslHandler.class) == null) {
            ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
        } else {
            ctx.write(new ChunkedNioFile(file.getChannel()));
        }

        ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
        // 如果没有请求keep_alive 则在写操作后关闭channel
        if (!keepAlive) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception {
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame);
            return;
        } else if (frame instanceof TextWebSocketFrame) {
            TextWebSocketFrame message = (TextWebSocketFrame) frame;
            if (message.text().indexOf("fileName:") != -1) {
                //fileName = RandomUtils.nextInt() + "_" + message.text();
                fileName = message.text();
                ctx.channel().writeAndFlush(new TextWebSocketFrame("ok"));
            } else if (message.text().indexOf("complete:") != -1) {
                ctx.channel().writeAndFlush(new TextWebSocketFrame("fileId:" + fileId));

            }

        } else if (frame instanceof BinaryWebSocketFrame) {
            BinaryWebSocketFrame msg = (BinaryWebSocketFrame) frame;
            byte[] body = new byte[msg.content().readableBytes()];
            msg.content().getBytes(0, body);
            String fileExtName = fileName.substring(fileName.lastIndexOf(".") + 1);
            if (fileId == null) {
                fileId = fastDFSTemplate.uploadAppenderFile("group1", body, fileExtName, null);
            } else {
                int result = fastDFSTemplate.append_file(fileId, body);
                if (result != 0) {
                    ctx.channel().writeAndFlush(new TextWebSocketFrame("failed"));
                }
            }
            ctx.channel().writeAndFlush(new TextWebSocketFrame("ok"));
        }
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {

        if (msg instanceof HttpRequest) {
            handleHttpRequest(ctx, (HttpRequest) msg);
        } else if (msg instanceof WebSocketFrame) {
            handleWebSocketFrame(ctx, (WebSocketFrame) msg);
        }

    }

    private static void sendHttpResponse(ChannelHandlerContext ctx, HttpRequest req, HttpResponse res) {
        ChannelFuture f = ctx.channel().writeAndFlush(res);
    }

    private static String getWebSocketLocation(HttpRequest req) {
        return "ws://" + req.headers().get(HttpHeaderNames.HOST) + WEBSOCKET_PATH;
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    private static void send100Continue(ChannelHandlerContext ctx) {
        // 100 - Continue 初始的请求已经接受，客户应当继续发送请求的其余部分
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
        ctx.writeAndFlush(response);
    }

}
